﻿/*
 * GridRenderer
 * 
 * initial implementation : 20.10.2008
 *
 * Copyright (c) Adam Sida, asida@centrum.cz.
 * All rights reserved.
 * 
 * for license see Petra.Gis.License.txt
 */
using System;
using System.Collections.Generic;
using System.Drawing;
//using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.Windows;
using System.Windows.Media;
using Petra.Gis.Geometry;
using Petra.Gis.Geometry.Support;
using Petra.Gis.Geometry.GridSupport;
using Petra.Gis.DataServices;
//using Petra.Gis.Drawing;
using Petra.Gis.RenderingServices.PlanarBase;
using Petra.Gis.Geometry.Utils;
using Petra.Gis.ReferenceSystemServices;
using Petra.Gis.ReferenceSystemServices.Transformations;


namespace Petra.Gis.RenderingServices.GDIPlus
{

  /// <summary>
  /// renders spatial referenced image (grid) into output map
  /// </summary>
  public partial class GridRenderer : BitmapMapRendererBase
  {


    //protected Pen _outpen;
    private System.Drawing.Imaging.ColorPalette _outPal;




    public GridRenderer()
      : base() { }





    //public GridRenderer(BitmapMap map, RenderStyle s)
    //  :base(map, s)
    //{

    //  //parse style
    //  //Pen _outpen = new Pen(Color.FromArgb((int)_style["Color"]), (int)_style["PointSize"]);

    //}




    ///// <summary>
    ///// method that implements drawing (conversion) of grid geometry into output format geometry
    ///// </summary>
    ///// <param name="geometry">any system type currently</param>
    //public override void RenderGeometry(GeometryBase geometry, SRSTransform forwardTransform, SRSTransform reverseTransform)
    //{
    //  if (!geometry.Valid)
    //    return;

    //  _forwardTransform = forwardTransform;
    //  _reverseTransform = reverseTransform;
    //  //validate type against supported types
    //  //if (geometry is Grid)
    //    renderGrid((Grid)geometry);
    //}





    /// <summary>
    /// method that implements drawing (conversion) of grid geometry into output format geometry
    /// </summary>
    /// <param name="geometry">any system type currently</param>
    public override void RenderGeometry(GeometryBase geometry)
    {
      if (!geometry.Valid)
        return;

      renderGrid((Grid)geometry);
    }





    protected virtual void renderGrid(Grid g)
    {

//      // temporary condition..
//      if (!_forwardTransform.Source.EqualParams(_forwardTransform.Target, CsCompareType.SkipStorageUnits | CsCompareType.SkipProjectionAxesOrientation))
//        throw new NotImplementedException(@"sorry image transform between different 
//                                            coordinate systems not supported now,
//                                            only storage units transform is supported");

      // select frame from griddata
      // where pixel size is closest to desired map pixel size
      // see MapBase.Resolution
      // example: for MapBase.Resolution = 100 (ppi), 1px on screen = 0.000254m in map units
      // which is: 0.000254m x MapBase.Scale = xx m in projection units
      //double aproxMetersPerCell = (MapPaperUnits.inch.MetersPerUnit / (1.0 * _map.Resolution)) * (1.0 / _map.Scale);
      double aproxMetersPerCell = (MapPaperUnit.inch.MetersPerUnit / (1.0 * rendererMap.Resolution)) * rendererMap.Scale;

      // first, select apropriate frame by pixelSize
      // if image has single frame only there might be
      // big difference between requested pixel size and image pixel size
      // raster image is always in projected CS
      GridFrameBase f = GridMath.SelectGridFrameByPixelSize(g, aproxMetersPerCell, (ProjectedCoordinateSystem)_forwardTransform.Source);

      // resolve pixel format for this frame
      // renderer specific pixel format (here GDI+)
      System.Drawing.Imaging.PixelFormat p = resolvePixelFormat(f.PixelFormat);

      // prepare palette if necessary
      if (f.Palette != null && f.Palette is System.Windows.Media.Imaging.BitmapPalette)
      {
        _outPal = preparePalette((System.Windows.Media.Imaging.BitmapPalette)f.Palette);
      }

      // select from grid data
      // only cells, visible in map range  
      // this call returns also updated bbox to fit whole pixels
      //BoundingBox bbb = _reverseTransform.TransformBox(_map.ProjectionExtent);
      //testMe(f, _reverseTransform.TransformBox(_map.ProjectionExtent));
      GridFrameExtent gfe = GridFrameExtent.Create(f, _reverseTransform.TransformBox(rendererMap.ProjectionExtent));

      // prepare bounding box for
      // image returned by GridFrameExtent
      BoundingBox tmpBbx = _forwardTransform.TransformBox(gfe.Bounds);

      // get transform into local var, nice syntax..
      DisplayTransformMatrix transform = (DisplayTransformMatrix)((BitmapMap)rendererMap).RenderTransform;

      // get image screen/display (pixel size), transform
      System.Drawing.Point outOrigin = transform.ProjectionToMap(tmpBbx.Left, tmpBbx.Top);
      System.Drawing.Point outSize = transform.ProjectionToMap(tmpBbx.Right, tmpBbx.Bottom);

      // if coordinate systems are same
      // then image can be directly drawn on screen
      if (_forwardTransform.Source.EqualParams(_forwardTransform.Target, CsCompareType.SkipStorageUnits | CsCompareType.SkipProjectionAxesOrientation))
      {
        // load data...
        Bitmap b = extractFrameDataByExtent(f, gfe, p);

        Rectangle outRect = new Rectangle(outOrigin.X, outOrigin.Y,
                                          outSize.X - outOrigin.X,
                                          outSize.Y - outOrigin.Y);

        // render to map
        _outGraphic.DrawImage(b, outRect);
      }
      // ok coordinate systems aree different
      // process tile-like image transform
      else 
      {
        // process either pixel transform
        // or either block transform
        // when source and target coordinate system differs
        //if (!g.Transform.Source.EqualParams(g.Transform.Target, true))
        //f = GridMath.TransformFrame(f, _forwardTransform);
        //gfe.
        // split screen area of image into A x A px sections
        // with 2px overlap
        // then for each A x A do transform
        int transfTileSize;
        if (rendererMap.DisplayPaperUnits == MapPaperUnit.px)
          // this value must not be fixed !!!
          transfTileSize = 40;
        else
          throw new NotImplementedException();

        //// todo: if image selection is smaller than transfTileSize in both dimensions (x,y)
        //// then only single transform is provided
        //if (gfe.GridPlaneBounds.Width <= transfTileSize && gfe.GridPlaneBounds.Height <= transfTileSize)
        //{
        //  throw new NotImplementedException();
        //}

        // step over all virtual tiles in image
        // with overlap of 2 px
        for(int y = outOrigin.Y; y <= outSize.Y + transfTileSize; y += (transfTileSize - 2))
          for (int x = outOrigin.X; x <= outSize.X + transfTileSize; x += (transfTileSize - 2))
          {
            // get bounds of current virtual tile
            // transform coordinates from screen/paper to projection
            // this is always normal cartesian transform
            Coordinate leftTop = transform.MapToProjection((float)x, (float)y);
            Coordinate RightBottom = transform.MapToProjection((float)(x + transfTileSize), (float)(y + transfTileSize));

            // get extent of this virtual tile in data source cs
            gfe = GridFrameExtent.Create(f, _reverseTransform.TransformBox(new BoundingBox(leftTop.X, leftTop.Y, RightBottom.X, RightBottom.Y)));

            // if outside of image range then skip this tile...
            if (gfe != null)
            {

              // instead of BoundingBox transform (which is always orthogonal)
              // transform 3 points back from tile
              // 1-------2
              //         |
              //         |
              //         3
              //
              // get diagonal angle before transform
              // in source cs
              Petra.Gis.Geometry.Support.Vector tmp = new Petra.Gis.Geometry.Support.Vector(
                gfe.Bounds.Right - gfe.Bounds.Left,
                gfe.Bounds.Bottom - gfe.Bounds.Top,
                0);

              //float a1 = GridMath.GetVectorCcwAngle(tmp);

              CoordStruct p1 = _forwardTransform.Transform(new CoordStruct(gfe.Bounds.Left, gfe.Bounds.Top));
              CoordStruct p2 = _forwardTransform.Transform(new CoordStruct(gfe.Bounds.Right, gfe.Bounds.Top));
              CoordStruct p3 = _forwardTransform.Transform(new CoordStruct(gfe.Bounds.Right, gfe.Bounds.Bottom));

              // prepare transform for these 3 points
              // rotate, translate by tile position, scale x,y
              Petra.Gis.Geometry.Support.Vector h = new Petra.Gis.Geometry.Support.Vector(p2.X - p1.X, p2.Y - p1.Y, 0);
              Petra.Gis.Geometry.Support.Vector v = new Petra.Gis.Geometry.Support.Vector(p3.X - p2.X, p3.Y - p2.Y, 0);

              //tmp = new Petra.Gis.Geometry.Support.Vector(
              //  v.I + h.I,
              //  v.J + h.J,
              //  0);
              //tmp = new Petra.Gis.Geometry.Support.Vector(h.I, h.J, 0);
              //float a2 = GridMath.GetVectorCcwAngle(tmp);
              //float angle = a2 - a1;

              float a2 = GridMath.GetVectorCcwAngle(h);
              float angle = a2;

              //double result = result1 + tmp;
              //if (result >= 360)
              //  result = result - 360;

              System.Drawing.Point sdp1 = transform.ProjectionToMap((float)p1.X, (float)p1.Y);
              System.Drawing.Point sdp2 = transform.ProjectionToMap((float)p2.X, (float)p2.Y);
              System.Drawing.Point sdp3 = transform.ProjectionToMap((float)p3.X, (float)p3.Y);

              float sclX = GridMath.GetScaleByVectorAndSize(new Petra.Gis.Geometry.Support.Vector(sdp2.X - sdp1.X, sdp2.Y - sdp1.Y, 0), gfe.GridPlaneBounds.Width);
              float sclY = GridMath.GetScaleByVectorAndSize(new Petra.Gis.Geometry.Support.Vector(sdp3.X - sdp2.X, sdp3.Y - sdp2.Y, 0), gfe.GridPlaneBounds.Height);

              if (sclX > 0.0 & sclY > 0.0)
              {
                // instead of orthogonal rectangle
                // compute only output origin of image location (left, top)
                //outOrigin = transform.ProjectionToMap(p1.X, p1.Y);
                // prepare transform
                _outGraphic.ResetTransform();
                _outGraphic.TranslateTransform(sdp1.X, sdp1.Y);
                _outGraphic.ScaleTransform(sclX, sclY);
                _outGraphic.RotateTransform(angle);

                // get data
                Bitmap b = extractFrameDataByExtent(f, gfe, p);

                // render to map
                _outGraphic.DrawImage(b, new System.Drawing.Point(0, 0));
              }

            }
          }

      }

    }





    public override DomainGeometryTypeEnum RendererGeometryDomain
    {
      get { return DomainGeometryTypeEnum.Grid; }
    }





    public override bool IsRenderable(GeometryBase geometry)
    {
      if (geometry is Grid)
        return true;
      else
        return false;
    }




    //protected Bitmap renderCells(GridCells c, System.Drawing.Imaging.PixelFormat p)
    //{
    //  Bitmap tmp = new Bitmap(c.Width, c.Height, p);

    //  System.Drawing.Imaging.BitmapData bd;
    //  bd = tmp.LockBits(new Rectangle(0, 0, c.Width, c.Height),
    //                    System.Drawing.Imaging.ImageLockMode.ReadWrite, p);

    //  int size = bd.Stride * bd.Height * (c.Source.Format.BitsPerPixel / 8);

    //  c.Source.CopyPixels(new System.Windows.Int32Rect(0, 0, c.Width, c.Height),
    //                      bd.Scan0, size, bd.Stride);

    //  return tmp;
    
    //}





    protected Bitmap extractFrameDataByExtent(GridFrameBase f, GridFrameExtent gfe, System.Drawing.Imaging.PixelFormat p)
    {

      // create output bitmap
      Bitmap tmp = new Bitmap(gfe.GridPlaneBounds.Width, gfe.GridPlaneBounds.Height, p);

      // if palette exist in frame, use prepared palette..
      if (f.Palette != null && f.Palette is System.Windows.Media.Imaging.BitmapPalette)
        tmp.Palette = _outPal;

      System.Drawing.Imaging.BitmapData bd;

      bd = tmp.LockBits(new Rectangle(0, 0, tmp.Width, tmp.Height),
                        System.Drawing.Imaging.ImageLockMode.WriteOnly, p);

      int size = bd.Stride * bd.Height;// *(f.PixelFormat.BitsPerPixel / 8);

      //((System.Windows.Media.Imaging.BitmapSource)f.Data).CopyPixels(
      f.Data.CopyPixels(
                          gfe.GridPlaneBounds,
                          bd.Scan0, size, bd.Stride);

      tmp.UnlockBits(bd);

      return tmp;

    }





    //protected unsafe Bitmap renderMultiBlockFrame(GridFrame f, System.Drawing.Imaging.PixelFormat p)
    //{
    //  // create temporary bitmap to hold selected image data
    //  Bitmap tmp = new Bitmap(f.Width, f.Height, p);

    //  if (f.Palette != null && f.Palette is System.Windows.Media.Imaging.BitmapPalette)
    //  {
    //    System.Drawing.Imaging.ColorPalette pal = tmp.Palette;
    //    for (int i = 0; i < ((System.Windows.Media.Imaging.BitmapPalette)f.Palette).Colors.Count; i++)
    //    //foreach (System.Windows.Media.Color c in (System.Windows.Media.Imaging.BitmapPalette)f.Palette)
    //    {
    //      System.Windows.Media.Color c = ((System.Windows.Media.Imaging.BitmapPalette)f.Palette).Colors[i];
    //      pal.Entries[i] = System.Drawing.Color.FromArgb(c.A, c.R, c.G, c.B);
    //      //pal.Entries[i] = System.Drawing.Color.FromArgb(0, 0, 0, 0);
    //    }
    //    tmp.Palette = pal;
    //  }
      

    //  //System.Windows.Media.Imaging.WriteableBitmap w;
    //  //if (f.Palette != null && f.Palette is System.Windows.Media.Imaging.BitmapPalette)
    //  //  w = new System.Windows.Media.Imaging.WriteableBitmap(
    //  //    f.Width, f.Height, 600, 600, f.PixelFormat, (System.Windows.Media.Imaging.BitmapPalette)f.Palette);
    //  //else
    //  //  w = new System.Windows.Media.Imaging.WriteableBitmap(
    //  //    f.Width, f.Height, 600, 600, f.PixelFormat, null);

    //  //w.BackBuffer
    //  //w.WritePixels(new System.Windows.Int32Rect(0, 0, b.PixelWidth, b.PixelHeight),

    //  // step over all blocks and draw its data into bitmap...
    //  //int lastBlockPosX, lastBlockPosY = 0;
    //  int size;
    //  System.Drawing.Imaging.BitmapData bd;
    //  GridBlock b = null;

    //  bd = tmp.LockBits(new Rectangle(0, 0, f.Width, f.Height),
    //              System.Drawing.Imaging.ImageLockMode.WriteOnly, p);

    //  size = bd.Stride * bd.Height * (f.PixelFormat.BitsPerPixel / 8);

    //  IntPtr tileColStart = bd.Scan0;
    //  IntPtr tileRowStart = bd.Scan0;

    //  for (int y = 0; y < f.NumBlocksVertical; y++)
    //  {
    //    //lastBlockPosX = 0;
    //    for (int x = 0; x < f.NumBlocksHorizontal; x++)
    //    {
    //      b = f.Blocks[(y * f.NumBlocksHorizontal) + x];

    //      if (y == 8 && x == 3)
    //        break;

    //      b.Source.CopyPixels(new System.Windows.Int32Rect(0, 0, b.PixelWidth, b.PixelHeight),
    //                          tileColStart, size, bd.Stride);

    //      //tmp.UnlockBits(bd);
    //      //lastBlockPosX += b.PixelWidth;
    //      tileColStart = (IntPtr)((long)tileColStart + b.PixelWidth);

    //      //tileStart += b.PixelWidth;

    //    }
    //    if (b != null)
    //    {
    //      //lastBlockPosY += b.PixelHeight; 
    //      tileRowStart = (IntPtr)((long)tileRowStart + (b.PixelHeight * bd.Stride));
    //      tileColStart = tileRowStart;
    //    }
    //  }

    //  tmp.UnlockBits(bd);

    //  return tmp;

    //}





    protected System.Drawing.Imaging.PixelFormat resolvePixelFormat(PixelFormat inputPf)
    {
      // resolve pixel format for this frame
      System.Drawing.Imaging.PixelFormat p;

      if ((inputPf.BitsPerPixel % 8) != 0)
        throw new ArgumentException(@"sorry, unsupported pixel format " + inputPf.ToString() +
                                     " must be modulo of 8");

      if (inputPf == PixelFormats.Indexed8)
        p = System.Drawing.Imaging.PixelFormat.Format8bppIndexed;
      else if (inputPf == PixelFormats.Rgb24)
        p = System.Drawing.Imaging.PixelFormat.Format24bppRgb;
      else
        throw new InvalidOperationException("unsupported GridFrame PixelFormat" + inputPf.ToString());

      return p;

    }




    private System.Drawing.Imaging.ColorPalette preparePalette(System.Windows.Media.Imaging.BitmapPalette pal)
    {

      Bitmap tmp = new Bitmap(1, 1, System.Drawing.Imaging.PixelFormat.Format8bppIndexed);

      System.Drawing.Imaging.ColorPalette tmpPal = tmp.Palette;
      for (int i = 0; i < pal.Colors.Count; i++)
      {
        System.Windows.Media.Color c = pal.Colors[i];
        tmpPal.Entries[i] = System.Drawing.Color.FromArgb(c.A, c.R, c.G, c.B);
      }

      return tmpPal;
    }





    protected override void prepareTransforms()
    {
      if (rendererMap != null && SpatialFieldMetadata != null)
      {
        _reverseTransform = rendererMap.SRSTransformCache.GetTransform(SpatialFieldMetadata.SRS, true);
        if (_reverseTransform.IsInversible)
          _forwardTransform = _reverseTransform.Inverse();
        else
          _forwardTransform = rendererMap.SRSTransformCache.GetTransform(SpatialFieldMetadata.SRS, false);
      }
    }





    //protected void testMe(GridFrame grFrame, BoundingBox aoiBounds)
    //{

    //  // update aoi first to tight to frame bounds
    //  // aoi is usually map range, transformed into
    //  // grid data coord system
    //  if (aoiBounds.Left < grFrame.Bounds.Left)
    //    aoiBounds.Left = grFrame.Bounds.Left;

    //  if (aoiBounds.Bottom < grFrame.Bounds.Bottom)
    //    aoiBounds.Bottom = grFrame.Bounds.Bottom;

    //  if (aoiBounds.Right < grFrame.Bounds.Right)
    //    aoiBounds.Right = grFrame.Bounds.Right;

    //  if (aoiBounds.Top < grFrame.Bounds.Top)
    //    aoiBounds.Top = grFrame.Bounds.Top;

    //  Int32Rect _gridPlaneBounds = new Int32Rect();

    //  double pixelWidth = grFrame.Bounds.Width / (grFrame.Width *  1.0);
    //  double pixelHeight = grFrame.Bounds.Height / (grFrame.Height * 1.0);

    //  _gridPlaneBounds.X = (int)((aoiBounds.Left - grFrame.Bounds.Left) / pixelWidth);
    //  _gridPlaneBounds.Y = grFrame.Height - (int)((aoiBounds.Top - grFrame.Bounds.Bottom) / pixelHeight);

    //  _gridPlaneBounds.Width = (int)((aoiBounds.Right - grFrame.Bounds.Left) / pixelWidth) - _gridPlaneBounds.X;
    //  _gridPlaneBounds.Height = (grFrame.Height - (int)((aoiBounds.Bottom - grFrame.Bounds.Bottom) / pixelHeight)) - _gridPlaneBounds.Y;


    //  // prepare world bounds
    //  BoundingBox _bounds = new BoundingBox();
    //  _bounds.Left = grFrame.Bounds.Left + (_gridPlaneBounds.X * pixelWidth);
    //  _bounds.Right = grFrame.Bounds.Left + ((_gridPlaneBounds.X + _gridPlaneBounds.Width) * pixelWidth);
    //  _bounds.Top = grFrame.Bounds.Bottom + ((grFrame.Height - _gridPlaneBounds.Y) * pixelHeight);
    //  _bounds.Bottom = grFrame.Bounds.Bottom + ((grFrame.Height - (_gridPlaneBounds.Y + _gridPlaneBounds.Height)) * pixelHeight); ;
    //}



  }
}
